/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author tien
 */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.Sprite;

public class Boy extends Moveable{
    private int health;
    private int mushroom;
    private int score;
    private boolean canGoHome;
    
    private boolean isInvi;
    private final int MAX_INVI_TIME = 1000; //1 second
    private int remainingInviTime;
    
    private static final int MIN_BUFFER = 20;
    private int viewPortX = 0;
    private int viewPortY = 0;
    
    public Boy(GameScreen canvas, Sprite sprite, SpriteAnimationTask animator) throws IOException {
        super(canvas, sprite, animator);
        
        health = GameConfig.MAXHEALTH;
        mushroom = GameConfig.MINMUSHROOM;
        score = GameConfig.INITSCORE;
        canGoHome = false;
        isInvi = false;
        remainingInviTime = MAX_INVI_TIME;
        
        viewPortX = getX();
        viewPortY = getY();
        adjustViewport(viewPortX - MIN_BUFFER, viewPortY - MIN_BUFFER);
    }
    
    void moveUp() {
        sprite.move(0, -speed);
        adjustViewport(viewPortX , viewPortY - speed);
    }

    void moveDown() {
        sprite.move(0, speed);
        adjustViewport(viewPortX , viewPortY + speed);
    }

    void moveLeft() {
        sprite.move(-speed, 0);
        adjustViewport(viewPortX  - speed, viewPortY);
    }

    void moveRight() {
        sprite.move(speed, 0);
        adjustViewport(viewPortX + speed, viewPortY);
    }
  
    public String getRMSName() {
        return "MushroomGameBoy";
    }

    void update() {
        int keyStates = canvas.getKeyStates();
        
        //if user is pressing the left button
        if ((keyStates & GameCanvas.LEFT_PRESSED) != 0) {
            this.direction = GameCanvas.LEFT;
            ((BoyController)this.controller).setMove(true);
        } else if ((keyStates & GameCanvas.RIGHT_PRESSED) != 0) {
            this.direction = GameCanvas.RIGHT;
            ((BoyController)this.controller).setMove(true);
        } else if ((keyStates & GameCanvas.UP_PRESSED) != 0) {
            this.direction = GameCanvas.UP;
            ((BoyController)this.controller).setMove(true);
        } else if ((keyStates & GameCanvas.DOWN_PRESSED) != 0) {
            this.direction = GameCanvas.DOWN;
            ((BoyController)this.controller).setMove(true);
        } else if ((keyStates & GameCanvas.FIRE_PRESSED) != 0) {
            ((BoyController)this.controller).setMove(false);
            ((BoyController)this.controller).setFire(true);
        } else {
            ((BoyController)this.controller).setMove(false);
            ((BoyController)this.controller).setFire(false);
        }
        this.controller.update();
        updateInvi();
    }
    
    /**
     * Adjust the viewport to keep the main animated sprite inside the screen.
     * The coordinates are checked for game boundary and adjusted only if it
     * makes sense.
     *
     * @param x viewport X coordinate
     * @param y viewport Y coordinate
     */
    public void adjustViewport(int x, int y) {

        int sx = getX();
        int sy = getY();

        int xmin = this.viewPortX + MIN_BUFFER;
        int xmax = this.viewPortX + canvas.getWidth() - getWidth() - MIN_BUFFER;
        int ymin = this.viewPortY + MIN_BUFFER;
        int ymax = this.viewPortY + canvas.getHeight() - getHeight() - MIN_BUFFER;

        //if the sprite is not near the any screen edges don't adjust
        if (sx >= xmin && sx <= xmax && sy >= ymin && sy <= ymax) {
            return;
        }

        //if the sprite is moving left but isn't near the left edge of the screen don't adjust
        if (this.previousDirection == GameCanvas.LEFT && sx >= xmin) {
            return;
        }
        //if the sprite is moving right but isn't near the right edge of the screen don't adjust
        if (this.previousDirection == GameCanvas.RIGHT && sx <= xmax) {
            return;
        }
        //if the sprite is moving up but isn't at near top edge of the screen don't adjust
        if (this.previousDirection == GameCanvas.UP && sy >= ymin) {
            return;
        }
        //if the sprite is moving down but isn't at near bottom edge of the screen don't adjust
        if (this.previousDirection == GameCanvas.DOWN && sy <= ymax) {
            return;
        }

        //only adjust x to values that ensure the base tiled layer remains visible
        //and no white space is shown
        if (x < canvas.getMap().getX()) {
            this.viewPortX = canvas.getMap().getX();
        } else if (x > canvas.getMap().getX() + canvas.getMap().getWidth() - canvas.getWidth()) {
            this.viewPortX = canvas.getMap().getX() + canvas.getMap().getWidth() - canvas.getWidth();
        } else {
            this.viewPortX = x;
        }

        //only adjust y to values that ensure the base tiled layer remains visible
        //and no white space is shown
        if (y < canvas.getMap().getY()) {
            this.viewPortY = canvas.getMap().getY();
        } else if (y > canvas.getMap().getY() + canvas.getMap().getHeight() - canvas.getHeight()) {
            this.viewPortY = canvas.getMap().getY() + canvas.getMap().getHeight() - canvas.getHeight();
        } else {
            this.viewPortY = y;
        }

        //adjust the viewport
        canvas.getLayerManager().setViewWindow(this.viewPortX, this.viewPortY, canvas.getWidth(), canvas.getHeight());
    }

    void die() {
        this.canvas.gameOver(false);
    }

    void stop() {
        super.stop();
    }

    void addScore(int score) {
        this.score += score;
        
        if (this.score < 0) {
            this.score = 0;
        }
    }

    void addHealth(int healthValue) {
        int healthAdd;
        if (healthValue < 0) {
            if (isInvi)
                healthAdd = 0;
            else {
                healthAdd = healthValue;
                isInvi = true;
                remainingInviTime = MAX_INVI_TIME;
            }
        }
        else {
            healthAdd = healthValue;
        }
        this.health += healthAdd;
        
        if (this.health <= 0) {
            die();
        }
        
        if (this.health > GameConfig.MAXHEALTH) {
            this.health = GameConfig.MAXHEALTH;
        }
    }

    void addMushroom(int i) {
        this.mushroom += i;
        if (this.mushroom >= GameConfig.MAXMUSHROOM) {
            this.canGoHome = true;
        }
    }
    
    public int getScore() {
        return this.score;
    }
    
    public int getHealth() {
        return this.health;
    }
    
    public int getMushroom() {
        return this.mushroom;
    }
    
    public boolean getCanGoHome() {
        return this.canGoHome;
    }

    void goHome() {
        this.canvas.gameOver(true);
    }

    void restart() {
        this.sprite.setPosition(this.canvas.getMap().home.x * this.canvas.map.tlThings.getCellWidth(), this.canvas.getMap().home.y* this.canvas.map.tlThings.getCellHeight());
        this.controller.restart();
        this.canGoHome = false;
        health = GameConfig.MAXHEALTH;
        mushroom = 0;
        score = GameConfig.INITSCORE;
    }

    private void updateInvi() {
        if (isInvi) {
            remainingInviTime -= canvas.getDeltaTime();
            
            if (remainingInviTime <= 0) {
                isInvi = false;
                remainingInviTime = MAX_INVI_TIME;
            }
        }
    }

    public boolean checkCollideAndSolve() {
        //Map
        boolean mapCollide =  canvas.getMap().checkCollide(sprite);
        
        //Things
        if (canvas.getMap().checkCollideWithThings(sprite)) {
            Vector thingIndexs = canvas.getMap().getThingIndexs(getX(), getY(), getWidth(), getHeight());
            for (int i = 0; i < thingIndexs.size(); i++) {
                int x = ((Point)thingIndexs.elementAt(i)).x;
                int y = ((Point)thingIndexs.elementAt(i)).y;
                int thing = canvas.getMap().getThing(x, y);
                canvas.boyPickup(thing, x, y);
            }
        }
        
        //Spiders
        boolean spidersCollide = canvas.spidersCollideWithBoy();
        if (spidersCollide) {
            //although boy collides with more than one spiders, boy lost health only one time.
            canvas.getBoy().addHealth(GameConfig.DAMAGEVALUE);
            canvas.getBoy().addScore(GameConfig.SCORELOSTWHENCOLLIDESPIDER);
        }
        
        return !(mapCollide || spidersCollide);
    }

    void loadBoy() throws Exception {
        try {
            // Will call either loadData() or createDefaultData()
            this.open();

            if (getNumRecords() > 0) {
                loadData();
            } else {
                createDefaultData();
            }

            // Close
            if (this.getRecordStore() != null)
                this.close();
            } catch (Exception e) {
            throw new Exception("Error load map" + e);
        }
    }
    
    
    
    void loadData() throws Exception {
        try {
            byte[] record = this.getRecordStore().getRecord(1);
            DataInputStream istream = new DataInputStream(new ByteArrayInputStream(record,0,record.length));

            this.sprite.setPosition(istream.readInt(), istream.readInt());
            this.direction = istream.readInt();
            this.previousDirection = istream.readInt();
            
            this.health = istream.readInt();
            this.mushroom = istream.readInt();
            this.score = istream.readInt();
            this.canGoHome = istream.readBoolean();
            
            this.isInvi = istream.readBoolean();
            this.remainingInviTime = istream.readInt();
        
        } catch (Exception e) {
          throw new Exception (this.getRMSName() + "::loadBoyData::" + e);
        }
    }

    void createDefaultData() throws Exception {
        try {
            ByteArrayOutputStream bstream = new ByteArrayOutputStream(12);
            DataOutputStream ostream = new DataOutputStream(bstream);
            
            Point point = new Point(0, 0);
            direction = GameCanvas.LEFT;
            previousDirection = GameCanvas.LEFT;
            
            health = GameConfig.MAXHEALTH;
            mushroom = GameConfig.MINMUSHROOM;
            score = GameConfig.INITSCORE;
            canGoHome = false;
            isInvi = false;
            remainingInviTime = MAX_INVI_TIME;
        
            ostream.writeInt(point.x);
            ostream.writeInt(point.y);
            ostream.writeInt(direction);
            ostream.writeInt(previousDirection);
            
            ostream.writeInt(health);
            ostream.writeInt(mushroom);
            ostream.writeInt(score);
            
            ostream.writeBoolean(canGoHome);
            ostream.writeBoolean(isInvi);
            ostream.writeInt(remainingInviTime);
            
            ostream.flush();
            ostream.close();
            byte[] record = bstream.toByteArray();
            this.getRecordStore().addRecord(record, 0, record.length);  
        } catch (Exception e) {
          throw new Exception(this.getRMSName() + "::createDefaultData::" + e);
        }
    }
    
    void updateData() throws Exception {
        try {
            ByteArrayOutputStream bstream = new ByteArrayOutputStream(12);
            DataOutputStream ostream = new DataOutputStream(bstream);
            
            ostream.writeInt(getX());
            ostream.writeInt(getY());
            ostream.writeInt(direction);
            ostream.writeInt(previousDirection);
            
            ostream.writeInt(health);
            ostream.writeInt(mushroom);
            ostream.writeInt(score);
            
            ostream.writeBoolean(canGoHome);
            ostream.writeBoolean(isInvi);
            ostream.writeInt(remainingInviTime);
            
            ostream.flush();
            ostream.close();
            byte[] record = bstream.toByteArray();
            this.getRecordStore().setRecord(1, record, 0, record.length);
        } catch (Exception e) {
            throw new Exception(this.getRMSName() + "::createDefaultData::" + e);
        }
    }
}
